import shlex, term, os, subprocess, sys

#region
# This will be moved to ProtoLib
import ctypes
from ctypes import c_long, c_wchar_p, c_ulong, c_void_p

gHandle = ctypes.windll.kernel32.GetStdHandle(c_long(-11))

def move (y, x):
   """Move cursor to position indicated by x and y."""
   value = x + (y << 16)
   ctypes.windll.kernel32.SetConsoleCursorPosition(gHandle, c_ulong(value))

def addstr (string):
   """Write string"""
   ctypes.windll.kernel32.WriteConsoleW(gHandle, c_wchar_p(string), c_ulong(len(string)), c_void_p(), None)
#endregion

def strtobool(val):
    """Convert a string representation of truth to true (1) or false (0).
    True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
    are 'n', 'no', 'f', 'false', 'off', and '0'.  Raises ValueError if
    'val' is anything else.
    """
    val = val.lower()
    if val in ('y', 'yes', 't', 'true', 'on', '1'):
        return True
    elif val in ('n', 'no', 'f', 'false', 'off', '0'):
        return False
    else:
        raise ValueError("invalid truth value %r" % (val,))

def _str(input):
    return str(input)

def _int(input):
    return int(input)

def _float(input):
    return float(input)

def _null(val):
    val = val.lower()
    if val in ('null', 'none', 'nil'):
        return None
    else:
        raise ValueError("invalid truth value %r" % (val,))

class FeatherScriptProcessor:
    def __init__(self):
        self.data = {}
        self.data_types = {
            'str': _str,
            'int': _int,
            'float': _float,
            'bool': strtobool,
            'null': _null,
        }
        self.function_return_value = None

    def process_string(self, input: str, replace_line_break_escape_sequence=True):
        out = input
        for name in self.data:
            if '$' + name in out:
                if self.data[name]['obj_type'] == 'function':
                    if '$' + name in input:
                        out = out.replace('$' + name, str(self.handle_function(name)))
                elif self.data[name]['obj_type'] == 'collide':
                    if '$' + name in input:
                        out = out.replace('$' + name, str(self.handle_collision(name)))
                else:
                    out = out.replace('$' + name, str(self.data[name]['value']))
        if replace_line_break_escape_sequence:
            out = out.replace('\\n', '\n')
        return out

    def handle_function(self, name):
        self.interpret_code(self.data[name]['value'])
        out = self.function_return_value
        self.function_return_value = None
        return out

    def handle_collision(self, name):
        return name

    def process_data_type(self, _type: str, data: str):
        if _type.lower() in self.data_types:
            return self.data_types[_type](data)
        else:
            return None

    def interpret_code(self, code: str):
        lines = code.strip().replace(";", "\n").split("\n")
        line_number = 0
        while line_number < len(lines):
            line = lines[line_number]
            # Remove comments from the line
            line = line.split("#")[0].strip()
            if not line:
                line_number += 1
                continue

            parts = shlex.split(line)
            # print(parts, line_number + 1) # DEBUG

            if parts[0].lower() == "echo":
                print(self.process_string(parts[1]))
            elif parts[0].startswith('$'):
                parts[0] = parts[0].split('$', 1)[1]
                if self.data[parts[0]]['obj_type'] == 'function':
                    self.handle_function(parts[0])
                elif self.data[parts[0]]['obj_type'] == 'collide':
                    self.handle_collision(parts[0])
                else:
                    raise TypeError(f'{self.data[parts[0]]["obj_type"]} can\'t be executed as function')
            elif parts[0].lower() == "write":
                term.write(self.process_string(parts[1]))
            elif parts[0].lower() == "cursor":
                if os.name == 'nt':
                    move(int(parts[2]), int(parts[1]))
                else:
                    term.pos(int(parts[1]), int(parts[2]))
            elif parts[0].lower() == "clear":
                if os.name == 'nt':
                    subprocess.run('cls')
                else:
                    subprocess.run('clear')
            elif parts[0].lower() == "exec":
                with open(parts[1]) as f:
                    script = f.read()
                    f.close()
                self.interpret_code(script)
            elif parts[0].lower() == "var":
                _content = self.process_data_type(parts[1].lower(), parts[3])
                _type = parts[1].lower()
                if parts[2] in self.data:
                    if self.data[parts[2]]['obj_type'] == 'constant':
                        print('A constant with that name has already been defined')
                        break
                    elif self.data[parts[2]]['obj_type'] == 'function':
                        print('A function with that name has already been defined')
                        break
                self.data[parts[2]] = {
                    'obj_type': 'variable',
                    'data_type': _type,
                    'value': _content
                }
            elif parts[0].lower() == "const":
                _content = self.process_data_type(parts[1].lower(), parts[3])
                _type = parts[1].lower()
                if parts[2] in self.data:
                    if self.data[parts[2]]['obj_type'] == 'constant':
                        print('A constant with that name has already been defined')
                        break
                    elif self.data[parts[2]]['obj_type'] == 'function':
                        print('A function with that name has already been defined')
                        break
                self.data[parts[2]] = {
                    'obj_type': 'constant',
                    'data_type': _type,
                    'value': _content
                }
            elif parts[0].lower() == "func":
                func = ''
                name = parts[1]
                line_number += 1
                while line_number < len(lines):
                    if lines[line_number].startswith('func'):
                        raise SyntaxError('Functions can\'t be defined in functions')
                    if lines[line_number].startswith('endfunc'):
                        break
                    func += lines[line_number] + '\n'
                    line_number += 1
                if name in self.data:
                    if self.data[name]['obj_type'] == 'constant':
                        raise ValueError('A constant with that name has already been defined')
                    elif self.data[name]['obj_type'] == 'function':
                        raise ValueError('A function with that name has already been defined')
                    elif self.data[name]['obj_type'] == 'collide':
                        raise ValueError('A colliding function with that name has already been defined')
                self.data[name] = {
                    'obj_type': 'function',
                    'data_type': 'str',
                    'value': func
                }
            elif parts[0].lower() == "collide":
                func = ''
                lang = parts[1]
                name = parts[2]
                line_number += 1
                while line_number < len(lines):
                    if lines[line_number].startswith('func'):
                        raise SyntaxError('Functions can\'t be defined in colliding functions')
                    elif lines[line_number].startswith('collide'):
                        raise SyntaxError('Colliding functions can\'t be defined in colliding functions')
                    if lines[line_number].startswith('endcollide'):
                        break
                    func += lines[line_number] + '\n'
                    line_number += 1
                if name in self.data:
                    if self.data[name]['obj_type'] == 'constant':
                        raise ValueError('A constant with that name has already been defined')
                    elif self.data[name]['obj_type'] == 'function':
                        raise ValueError('A function with that name has already been defined')
                    elif self.data[name]['obj_type'] == 'collide':
                        raise ValueError('A colliding function with that name has already been defined')
                self.data[name] = {
                    'obj_type': 'collide',
                    'data_type': 'str',
                    'value': func,
                    'lang': lang
                }
            elif parts[0].lower() == "if":
                exec_code = ''
                a, b = None, None
                if parts[1].startswith('$'):
                    a = str(self.process_string(parts[1]))
                else:
                    a = str(self.process_data_type(*parts[1].split(':', 1)))
                if parts[3].startswith('$'):
                    b = str(self.process_string(parts[3]))
                else:
                    b = str(self.process_data_type(*parts[3].split(':', 1)))
                def handle_true(line_number, exec_code):
                    line_number += 1
                    while line_number < len(lines):
                        if lines[line_number].startswith('endif') or lines[line_number].startswith('else'):
                            break
                        if lines[line_number].startswith('func'):
                            raise SyntaxError('Functions can\'t be defined in if statements')
                        elif lines[line_number].startswith('collide'):
                            raise SyntaxError('Colliding functions can\'t be defined in if statements')
                        exec_code += lines[line_number] + '\n'
                        line_number += 1
                    if lines[line_number].startswith('else'):
                        line_number += 1
                        while line_number < len(lines):
                            if lines[line_number].startswith('endif'):
                                break
                            line_number += 1
                    return line_number, exec_code
                def handle_false(line_number, exec_code):
                    while line_number < len(lines):
                        if lines[line_number].startswith('func'):
                            raise SyntaxError('Functions can\'t be defined in if statements')
                        elif lines[line_number].startswith('collide'):
                            raise SyntaxError('Colliding functions can\'t be defined in if statements')
                        if lines[line_number].startswith('endif'):
                            break
                        if lines[line_number].startswith('else'):
                            line_number += 1
                            while line_number < len(lines):
                                if lines[line_number].startswith('endif'):
                                    break
                                exec_code += lines[line_number] + '\n'
                                line_number += 1
                        line_number += 1
                    return line_number, exec_code
                if parts[2] == '==':
                    if a == b:
                        line_number, exec_code = handle_true(line_number, exec_code)
                    else:
                        line_number, exec_code = handle_false(line_number, exec_code)
                if parts[2] == '!=':
                    if a != b:
                        line_number, exec_code = handle_true(line_number, exec_code)
                    else:
                        line_number, exec_code = handle_false(line_number, exec_code)
                self.interpret_code(exec_code)
                line_number += 1
            elif parts[0].lower() == "return":
                out = self.process_data_type(parts[1], parts[2])
                self.function_return_value = out
            elif parts[0].lower() == "endif" or parts[0].lower() == "endfunc" or parts[0].lower() == "endcollide" or parts[0].lower() == "else": # unexpected keyword
                raise SyntaxError(f'Unexpected {parts[0]}')
            elif parts[0].lower() == "jump":
                line_number = int(parts[1]) - 1
                continue
            elif parts[0].lower() == "skip":
                line_number += 1
            elif parts[0].lower() == "halt":
                print(f'Code execution loop stopped at line {line_number}')
                break
            elif parts[0].lower() == "quit":
                if len(parts) >= 1:
                    sys.exit(parts[1])
                else:
                    sys.exit()
            else:
                raise SyntaxError(f"Invalid command: {parts[0]}")
            line_number += 1
